查看原文
其他

Go语言Interface源码级解读

Go开发大全 2021-07-20

(给Go开发大全加星标)

来源:无敌的CF

https://zhuanlan.zhihu.com/p/86420182

【导读】golang的interface到底是什么?怎么用?它的底层是如何实现的?字节程序带你深入理解interface。

源码

源码基于go v1.13

go的interface有两种,eface和iface

// $GOROOT/src/runtime/runtime2.go

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

eface结构体

eface比较简单,代表的是没有方法的interface,interface{}

eface有两个属性,一个是_type,一个是data。data就是一个指针指向实际的值。type的结构如下

// $GOROOT/src/runtime/type.go

type _type struct {
    size       uintptr
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32
    tflag      tflag
    align      uint8
    fieldalign uint8
    kind       uint8
    alg        *typeAlg
    // gcdata stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, gcdata is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

type的意义就是表明interface所存储的值的具体类型(concrete type)。

iface结构体

iface也有两个属性,tabdata。data和eface中的是一样的。itab的结构如下

// $GOROOT/src/runtime/runtime2.go

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

// $GOROOT/src/runtime/type.go
type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}

其中interfacetype是描述interface自身的类型,而_type字段和eface中的type类似,描述的是具体类型。fun是指向interface方法集的指针,go通过它实现Dynamic Dispatch。

看看反射(reflect)

顾名思义,反射中的reflect.TypeOf()reflect.ValueOf()就对应interface的两个属性。它们的源码如下:

// $GOROOT/src/reflect/type.go

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

// $GOROOT/src/reflect/value.go

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
    typ  *rtype
    word unsafe.Pointer
}

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }

    // TODO: Maybe allow contents of a Value to live on the stack.
    // For now we make the contents always escape to the heap. It
    // makes life easier in a few places (see chanrecv/mapassign
    // comment below).
    escapes(i)

    return unpackEface(i)
}

// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
    e := (*emptyInterface)(unsafe.Pointer(&i))
    // NOTE: don't read e.word until we know whether it is really a pointer or not.
    t := e.typ
    if t == nil {
        return Value{}
    }
    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f}
}

先从TypeOf说起。Go的函数都是值传递,参数传入TypeOf的之前,会先被转换为interface{}类型。若传入参数自身为interface类型,则会把自身的具体类型(concret type)存储至新的eface结构体的_type字段之中。之后用unsafe.Pointer做强制类型转换,将eface结构体映射到emptyInterface上,这两个结构体拥有完全一样的结构。之后便可以从emptyInterface中获得类型的数据。这里面rtype的结构也必须和runtime中的_type完全一样。reflect.Type是一个interface,rtype实现了它的所有方法。返回的时候,go会把rtype结构体转换成reflect.Type返回。

再说回ValueOf方法。reflect.Value是个结构体。flag的部分先忽略掉。Value的结构其实就和eface很像了。从interface{}得到reflect.Value的过程也就比较直接。

翻译自官方Q&A

为什么类型T不能满足Equalinterface

如下是一个简单的interface,可以用自身与另一个值进行比较:

type Equaler interface {
    Equal(Equaler) bool
}

然后如下是类型T:

type T int
func (t T) Equal(u T) bool { return t == u } // 并不满足Equaler

与一些支持多态(polymorphic)的类型系统不一样,T并没有实现Equaler。T.Equal的参数类型是T,字面上并不是interface定义的Equaler类型。

在Go中,类型系统不会进一步判断Equal的参数的类型;就像如下实现了Equaler的类型T2所示,这是程序员的职责。

type T2 int
func (t T2) Equal(u Equaler) bool { return t == u.(T2) }  // 满足 Equaler

在Go中,因为任何满足Equaler的类型都可以作为参数传入T2.Equal,所以我们必须在运行时(run time)来检查参数的类型是否是T2. 一些语言在编译时(compile time)就可以确保这一点。

如下的例子从另一个侧面证明了这一点:

type Opener interface {
   Open() Reader
}

func (t T3) Open() *os.File

在Go中, T3并没有满足Opener, 尽管在许多语言中,这是成立的。

尽管在这些例子中,Go的类型系统确实对程序员的帮助少了一些,但是舍弃掉子类型(subtyping)使得我们可以很容易说清interface的满足规则:是否方法的名字和签名和interface的完全一致。这样的规则也更容易实现。我们认为它的好处抵消了坏处。是否Go要在某一天实现某种形式的多态,我们会持续观察。

我可以把[]T转换成[]interface{}

无法直接做到。Go的语言规范不允许这样做,因为两种类型在内存中的表现是不同的。如果要转换的话,我们必须要把每个元素分别拷贝到目标切片。如下面例子所示:

t := []int{1234}
s := make([]interface{}, len(t))
for i, v := range t {
    s[i] = v
}

为什么我的nil error不等于nil

只有当一个interface的V和T都unset时,它的值才是nil,换言之,一个nil interface的type一定是nil。如果我们把一个类型为*int的nil指针存放于一个interface中,这个变量的内部类型会是*int。这样的interface是非nil的,尽管它存储的值是nil。

当一个nil值被存储在一个interface中的时候就会发生这种不好理解的现象。一个典型的例子就是错误返回:

func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = ErrBad
    }
    return p // Will always return a non-nil error.
}

这个方法永远会返回一个非nil的错误。想要向调用者正确的返回nil error,我们需要显式地返回nil:

func returnsError() error {
    if bad() {
        return ErrBad
    }
    return nil
}

在函数签名的错误返回中使用error类型而不是使用具体类型比如*MyError会是一个好主意(如上所示),这能帮助确保错误是正确创建的。

写个小感想

  • interface的使用是要额外分配内存的,是会对性能产生影响的。当我们使用它的时候,主要是为了实现一定的灵活性,某种程度的范型,或者为了隐藏具体实现。在很关注性能的情况下,需要尽量少用interface,更要少用反射。
  • 同样是nil,同样是.,他们在涉及interface的时候是有不同的含义的。nil很复杂,值得单写一篇文章专门去聊。而.操作,在对应interface的时候,调用的实际方法是runtime才可以确定的。


 - EOF -

推荐阅读(点击标题可打开)

1、Go pprof 性能分析

2、如何使用 dlv 调试 Golang 程序?

3、Golang中的nil,没有人比我更懂nil!


Go 开发大全

参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。

关注后获取

回复 Go 获取6万star的Go资源库



分享、点赞和在看

支持我们分享更多好文章,谢谢!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存